package gggameserver.games.khet;

import gggameserver.exceptions.GGIllegalFormat;
import gggameserver.exceptions.GGIllegalMoveFormat;
import gggameserver.games.Board.GameEndReason;
import gggameserver.games.Position;
import gggameserver.games.khet.KhetBoard.GameEndReasonDetails;
import gggameserver.games.khet.KhetCommandFormatter.GameStatus;
import gggameserver.games.khet.KhetPiece.Type;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.StringTokenizer;

public class KhetCommandReader {

	// TODO readable error-messages on parsing failure

	public static KhetMove getMove(String move) throws GGIllegalMoveFormat {
		if (null == move)
			throw new GGIllegalMoveFormat(move);
		if (move.length() != 4)
			throw new GGIllegalMoveFormat(move);

		// move example: 97c
		int player_nr;
		int x;
		int y;
		int action;

		try {
			// player
			player_nr = decodePlayerColor(move.substring(0, 1), "player number");

			// position...
			x = Integer.parseInt(move.substring(1, 2), 10);
			y = Integer.parseInt(move.substring(2, 3), 10);

			// action
			String actionStr = move.substring(3, 4);
			if (KhetCommandFormatter.MOVE_TURN_CLOCKWISE.equals(actionStr))
				action = KhetMove.ACTION_TURN_CLOCKWISE;
			else if (KhetCommandFormatter.MOVE_TURN_COUNTERCLOCKWISE
					.equals(actionStr))
				action = KhetMove.ACTION_TURN_COUNTERCLOCKWISE;
			else
				action = Integer.parseInt(actionStr, 10);
		} catch (NumberFormatException e) {
			throw new GGIllegalMoveFormat(move);
		} catch (GGIllegalFormat e) {
			// decodePlayerColor
			throw new GGIllegalMoveFormat(move);
		}

		return new KhetMove(player_nr, x, y, action);
	}

	public static KhetBoard getBoardFromString(String cmd)
			throws GGIllegalFormat {
		// final result: R S - 97c 1 26 R00X2 R40A2 R50P2 ... S97X3
		StringTokenizer st = new StringTokenizer(cmd);

		// game status...
		GameStatus status = readGameStatus(st);
		// next player...
		int playerNumber = readPlayerNumber(st);
		// game (end) result...
		String gameResultStr = st.nextToken();
		// last move...
		Map.Entry<KhetMove, KhetShotResult> lastMove = readLastMove(st);
		// moves done...
		int movesDone = 0;
		st.nextToken(); // TODO
		// pieces
		ArrayList<KhetPiece> pieces = readPieces(st);

		KhetBoard kboard = KhetBoard.createBoard(pieces, playerNumber, null,
				lastMove);
		parseResult(gameResultStr, kboard);

		return kboard;
	}

	//

	private static void parseResult(String resultStr, KhetBoard kboard)
			throws GGIllegalFormat {
		if (resultStr.equals(KhetCommandFormatter.GAME_END_GAME_RUNNING))
			return;

		// winner
		String winner = resultStr.substring(0, 1);
		if (KhetCommandFormatter.GAME_DRAW.equals(winner)) {
			kboard.setDraw();
		} else {
			kboard.setWinner(decodePlayerColor(winner, "winner"));
		}

		// end reason
		if (resultStr.length() < 2)
			return;

		String reason = resultStr.substring(1, 2);
		if (reason.equals(KhetCommandFormatter.GAME_END_REASON_TIMEOUT))
			kboard.setGameEndReason(GameEndReason.TIMEOUT);
		else if (reason
				.equals(KhetCommandFormatter.GAME_END_REASON_ILLEGAL_MOVE))
			kboard.setGameEndReason(GameEndReason.ILLEGAL_MOVE);
		else if (reason.equals(KhetCommandFormatter.GAME_END_REASON_NORMAL))
			kboard.setGameEndReason(GameEndReason.NORMAL);
		else
			throw new GGIllegalFormat("Illegal game result '" + resultStr
					+ "': illegal reason '" + reason + "'");

		// end reason details
		if (resultStr.length() < 3)
			return;

		String reasonDetail = resultStr.substring(2, 3);
		if (reasonDetail
				.equals(KhetCommandFormatter.GAME_END_REASON_DETAIL_MOVE_LIMIT_EXCEEDED))
			kboard.setGameEndReasonDetails(GameEndReasonDetails.MOVE_LIMIT_EXCEEDED);
		else if (reasonDetail
				.equals(KhetCommandFormatter.GAME_END_REASON_DETAIL_3D_SITUATION))
			kboard.setGameEndReasonDetails(GameEndReasonDetails.SITUATION_3D);
		else
			throw new GGIllegalFormat("Illegal game result '" + resultStr
					+ "': illegal reason detail '" + reason + "'");
	}

	private static ArrayList<KhetPiece> readPieces(StringTokenizer st)
			throws GGIllegalFormat {
		int pieceCnt;
		try {
			if (!st.hasMoreTokens())
				throw new GGIllegalFormat("Piece count not present");
			pieceCnt = Integer.parseInt(st.nextToken());
		} catch (NumberFormatException e) {
			throw new GGIllegalFormat("Illegal piece count " + e.getMessage());
		}

		// reading pieces
		ArrayList<KhetPiece> pieces = new ArrayList<KhetPiece>();

		for (int i = 0; i < pieceCnt; ++i) {
			if (!st.hasMoreTokens())
				throw new GGIllegalFormat("Description for piece number " + i
						+ " not present");
			// R40A2
			String pieceStr = st.nextToken();

			KhetPiece piece;
			try {
				piece = decodePiece(pieceStr);
			} catch (GGIllegalFormat e) {
				throw new GGIllegalFormat("Can't read " + i + ". piece: "
						+ e.getMessage());
			}
			pieces.add(piece);
		}

		return pieces;
	}

	private static KhetPiece decodePiece(String pieceStr)
			throws GGIllegalFormat {
		int playerNr;
		int x;
		int y;
		KhetPiece.Type type;
		int direction;

		try {
			playerNr = decodePlayerColor(pieceStr.substring(0, 1),
					"piece color");
			x = Integer.parseInt(pieceStr.substring(1, 2));
			y = Integer.parseInt(pieceStr.substring(2, 3));
			type = decodePieceType(pieceStr.substring(3, 4));
			direction = Integer.parseInt(pieceStr.substring(4, 5));
		} catch (NumberFormatException e) {
			throw new GGIllegalFormat("Can't decode piece: " + e.getMessage());
		}

		KhetPiece piece = new KhetPiece(playerNr, type, x, y, direction);
		try {
			piece.check();
		} catch (IllegalStateException e) {
			throw new GGIllegalFormat("Can't decode piece: " + e.getMessage());
		}
		return piece;
	}

	private static GameStatus readGameStatus(StringTokenizer st)
			throws GGIllegalFormat {
		if (!st.hasMoreTokens())
			throw new GGIllegalFormat("Game status not present");

		String statusStr = st.nextToken();
		if (statusStr.equals(KhetCommandFormatter.GAME_STATUS_STARTING))
			return GameStatus.Starting;
		if (statusStr.equals(KhetCommandFormatter.GAME_STATUS_RUNNING))
			return GameStatus.Running;
		if (statusStr.equals(KhetCommandFormatter.GAME_STATUS_ENDED))
			return GameStatus.Ended;

		throw new GGIllegalFormat("Illegal game status '" + statusStr + "'");
	}

	private static int readPlayerNumber(StringTokenizer st)
			throws GGIllegalFormat {
		if (!st.hasMoreTokens())
			throw new GGIllegalFormat("Player number not present");

		String statusStr = st.nextToken();
		return decodePlayerColor(statusStr, "player number");
	}

	private static Map.Entry<KhetMove, KhetShotResult> readLastMove(
			StringTokenizer st) throws GGIllegalFormat {
		if (!st.hasMoreTokens())
			throw new GGIllegalFormat("Last move not present");

		String moveStr = st.nextToken();
		if (KhetCommandFormatter.MOVE_NO_MOVE.equals(moveStr))
			return null;

		// move exists, parse
		StringTokenizer moveTokenizer = new StringTokenizer(moveStr,
				KhetCommandFormatter.MOVE_SHOT_SEPARATOR); // TODO allows
															// infinite #s

		KhetMove move;
		try {
			if (!moveTokenizer.hasMoreTokens())
				throw new GGIllegalFormat("Illegal last move: '" + moveStr
						+ "'");
			move = getMove(moveTokenizer.nextToken());
		} catch (GGIllegalMoveFormat e) {
			throw new GGIllegalFormat(e.getMessage());
		}

		// parse shot
		KhetShotResult shot = null;
		if (moveTokenizer.hasMoreTokens())
			shot = getShot(moveTokenizer.nextToken());

		return new AbstractMap.SimpleEntry(move, shot);
	}

	private static KhetShotResult getShot(String shotStr)
			throws GGIllegalFormat {
		if (shotStr.length() < 2)
			throw new GGIllegalFormat("Shot description too short: '" + shotStr
					+ "'");

		try {
			// TODO check value
			int x = Integer.parseInt(shotStr.substring(0, 1));
			int y = Integer.parseInt(shotStr.substring(1, 2));

			ArrayList<Position> trajectory = new ArrayList<Position>();
			trajectory.add(new Position(x, y));
			// , KhetPiece piece

			int pos = 2;
			while (pos < shotStr.length()) {
				Position newPos = new Position(
						trajectory.get(trajectory.size() - 1));

				String token = shotStr.substring(pos, pos + 1);

				if (token.equals(KhetCommandFormatter.SHOT_KILL_SEPARATOR)) {
					++pos;
					break;
				}

				int direction = Integer.parseInt(token);
				newPos.add(KhetBoard.getDirectionDMove(direction));

				trajectory.add(newPos);
				++pos;
			}

			KhetPiece piece = null;
			if (pos < shotStr.length())
				piece = decodePiece(shotStr.substring(pos));

			return new KhetShotResult(trajectory, piece);
		} catch (Exception e) {
			throw new GGIllegalFormat("Illegal shot description: '" + shotStr
					+ "'");
		}
	}

	private static int decodePlayerColor(String s, String fieldName)
			throws GGIllegalFormat {
		if (KhetCommandFormatter.PLAYER_COLOR_SILVER.equals(s))
			return KhetBoard.PLAYER_SILVER;
		if (KhetCommandFormatter.PLAYER_COLOR_RED.equals(s))
			return KhetBoard.PLAYER_RED;
		throw new GGIllegalFormat("Illegal " + fieldName + " '" + s + "'");
	}

	private static Type decodePieceType(String stype) throws GGIllegalFormat {
		if (KhetCommandFormatter.PIECE_TYPE_PYRAMID.equals(stype))
			return KhetPiece.Type.PYRAMID;
		if (KhetCommandFormatter.PIECE_TYPE_PHARAON.equals(stype))
			return KhetPiece.Type.PHARAON;
		if (KhetCommandFormatter.PIECE_TYPE_ANUBIS.equals(stype))
			return KhetPiece.Type.ANUBIS;
		if (KhetCommandFormatter.PIECE_TYPE_SCARAB.equals(stype))
			return KhetPiece.Type.SCARAB;
		if (KhetCommandFormatter.PIECE_TYPE_SPHINX.equals(stype))
			return KhetPiece.Type.SPHINX;
		throw new GGIllegalFormat("Illegal piece type string '" + stype + "'");
	}

}
